Es gibt verschiedene Möglichkeiten um Karten in ggplot2 zu erstellen:

  • Polygon maps (simpel)
  • Simple features maps (komplexer)

Foto von Annie Spratt auf Unsplash

Simple Features

Bestimmtes Encoding für Geodaten, das häufig verwendet wird. Um mit simple features arbeiten zu könnnen benötigt man das Paket sf.

# install.packages(c("sf", "tidyverse"))
library(tidyverse)
library(sf)

Als Beispiel nutzen wir Daten zur Bundestagswahl in Deutschland aus dem gerda-Paket.

# install.packages("gerda")
library(gerda)

Datenaufbereitung

Hier habe ich zwei Datensätze heruntergeladen, ebenfalls aus dem GERDA-Projekt. Ihr könnt sie hier downloaden (Ihr braucht alle Dateien im Ordner).

## Für die Gemeindegrenzen
geom_dat <- sf::read_sf(here::here("sessions", "good_plots", "data", "shapefiles"), layer = "VG250_GEM")

## Für die Bundeslandgrenzen
geom_dat_lan <- sf::read_sf(here::here("sessions", "good_plots", "data", "shapefiles"), layer = "VG250_LAN") %>%
  dplyr::filter(GF == 4)

Zusätzlich brauchen wir noch die Datensätze zu den Wahlergebnissen der Bundestagswahl 2025:

elect_dat <- load_gerda_web("federal_muni_unharm", verbose = FALSE, file_format = "rds") 

## Aufbereitung
elect_dat <- elect_dat %>%
  dplyr::filter(election_year == 2025) %>%
  dplyr::select(ags, county, cdu_csu, spd, gruene, afd, bsw, fdp, linke_pds, number_voters, eligible_voters)

## Mergen an die Geo-Daten
geom_elect_dat <- geom_dat %>%
  left_join(elect_dat, by = c("AGS" = "ags")) %>%
  dplyr::filter(GF == 4)

Datenaufbereitung

Ich wähle hier die Partei mit den meisten Zweitstimmen pro Gemeinde aus:

geom_elect_max <- geom_elect_dat %>%
  pivot_longer(
    cols = c("cdu_csu", "spd", "gruene", "afd", "linke_pds"), values_to = "result",
    names_to = "partei"
  ) %>%
  filter(!is.na(result)) %>%
  group_by(AGS) %>%
  slice_max(result)

Karte

Die Funktion geom_sf() kann aus unseren sf-Daten direkt eine Karte erstellen:

p <- ggplot() +
  geom_sf(data = geom_elect_max)

p

Mit Bundeslandgrenzen:

(das war unser zweite Datensatz)

ggplot() +
  geom_sf(data = geom_dat_lan)

Zusammenfügen:

ggplot() +
  geom_sf(data = geom_elect_max, color = NA) +
  geom_sf(data = geom_dat_lan, fill = NA, colour = "grey30", linewidth = 0.2) +
  coord_sf() +
  theme_minimal()

Einfärben

Da wir ja die interressierenden Daten direkt an die Geo-Daten rangehängt haben, können wir sie ganz einfach in unseren ggplot mit aufnehmen. Farben setze ich anhand dieser Tabelle.

ggplot() +
  geom_sf(data = geom_elect_max, aes(fill = partei), color = NA) +
  geom_sf(data = geom_dat_lan, fill = NA, colour = "grey30", linewidth = 0.2) +
  coord_sf() +
  theme_minimal() +
  scale_fill_manual(values = c(
    "cdu_csu" = "#000000",
    "spd" = "#E3000F",
    "gruene" = "#1AA037",
    "linke_pds" = "purple",
    "afd" = "#0489DB"
  )) +
  labs(title = "Partei mit den meisten Zweitstimmen pro Gemeinde")

Alternativen

Vielleicht ist es informativer, die Wahlergebnisse parteienspezifisch zu zeigen:

## Daten ins Long-Format
geom_elect_dat_long <- geom_elect_dat %>%
  pivot_longer(
    cols = c("cdu_csu", "spd", "gruene", "afd", "linke_pds"), values_to = "result",
    names_to = "partei"
  ) %>% 
  drop_na(result)

Parteispezifischer Plot

ggplot() +
  geom_sf(data = geom_elect_dat_long, aes(fill = partei, alpha = result), color = NA) +
  geom_sf(data = geom_dat_lan, fill = NA, colour = "grey30", linewidth = 0.2) +
  coord_sf() +
  facet_wrap(~partei) +
  theme_minimal() +
  scale_fill_manual(values = c(
    "cdu_csu" = "#000000",
    "spd" = "#E3000F",
    "gruene" = "#1AA037",
    "linke_pds" = "purple",
    "afd" = "#0489DB"
  )) 

Hier muss die Farbwahl gut bedacht werden. Aktuell ist der Alpha-Wert für alle Parteien gleich, was dazu führt, dass Parteien mit weniger Stimmen sehr blass dargestellt werden. Die Zeit hat die Farbgebung etwas anders gelöst.

Alternativen

Wie bereits besprochen kann es Nachteile haben, Nicht-Geodaten als Geo-Daten darzustellen. Z.B. entspricht die Größe der Fläche nicht der Anzahl der Stimmen. Links seht ihr einige Beispiele dafür, wie man damit umgehen könnte.
Es ist dabei immer ein Trade-off zwischen Akkuratheit der Geo-Informationen, und Visualisierung der Information die man eigentlich zeigen möchte. Siehe storymaps.